﻿using System;

using System.Reflection;
using System.ComponentModel;

using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Interop;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Xna = Microsoft.Xna.Framework;
using Wpf = System.Windows;

using System.Collections.Generic;

namespace PotatoSoft.CustomControl
{
    public partial class XnaControl : UserControl
    {
        private PresentationParameters presentationParameters;
        private GraphicsDevice graphicsDevice;
        private RenderTarget2D renderTarget;
        private HwndSource deviceWindow;

        private static bool firstRun = true;

        private static readonly RoutedEvent RenderEvent = EventManager.RegisterRoutedEvent("Render", RoutingStrategy.Bubble, typeof(RenderRoutedEventHandler), typeof(XnaControl));
        private static readonly RoutedEvent DeviceCreatedEvent = EventManager.RegisterRoutedEvent("DeviceCreated", RoutingStrategy.Bubble, typeof(DeviceCreatedRoutedEventHandler), typeof(XnaControl));

        public XnaControl()
        {
            InitializeComponent();
        }

        public event RenderRoutedEventHandler Render
        {
            add
            {
                base.AddHandler(XnaControl.RenderEvent, value);
            }
            remove
            {
                base.RemoveHandler(XnaControl.RenderEvent, value);
            }
        }

        public event DeviceCreatedRoutedEventHandler DeviceCreated
        {
            add
            {
                base.AddHandler(XnaControl.DeviceCreatedEvent, value);
            }
            remove
            {
                base.RemoveHandler(XnaControl.DeviceCreatedEvent, value);
            }
        }

        #region UserControl Events

        /// <summary>
        /// Metoda wywoływana jest podczas ładowania kontrolki. Inicjalizuje wstępnie GraphicsDevice.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            // initialize only if isn't in design mode
            if (DesignerProperties.GetIsInDesignMode(this) == false)
            {
                this.InitializeGraphics();

                DeviceCreatedRoutedEventArgs eventArgs = new DeviceCreatedRoutedEventArgs(XnaControl.DeviceCreatedEvent, this.graphicsDevice);
                this.RaiseEvent(eventArgs);

                CompositionTarget.Rendering += new EventHandler(OnRendering);

                /* Initialize, LoadContent Start */
                //TODO: raise event initialized - or deviceCreated ?
                /* Initialize, LoadContent End */
            }
        }

        bool inDeviceTransition = false;

        /// <summary>
        /// Reakcja na zmianę rozmiaru okna. Resetuje GraphicsDevice, zmienia rozdzielczość wyświetlania.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (firstRun) { firstRun = false; return; }
            if (inDeviceTransition) { return; }

            if (e.NewSize.Width > 0 && e.NewSize.Height > 0)
            {
                if (e.NewSize.Width != e.PreviousSize.Width || e.NewSize.Height != e.PreviousSize.Height)
                {
                    PresentationParameters pp = this.graphicsDevice.PresentationParameters;
                    pp.BackBufferWidth = (int)e.NewSize.Width;
                    pp.BackBufferHeight = (int)e.NewSize.Height;

                    this.inDeviceTransition = true;

                    this.graphicsDevice.Reset(pp);

                    this.inDeviceTransition = false;

                    //bool resetedDevice = false;
                    //if (this.graphicsDevice != null)
                    //{
                    //    try
                    //    {
                    //        this.graphicsDevice.Reset(pp);
                    //        resetedDevice = true;
                    //    }
                    //    catch (Exception)
                    //    {
                    //        ; // jeśli się coś nie uda to olewamy - stworzymy na nowo GraphicsDevice
                    //    }
                    //}

                    //if (!resetedDevice) /* nie udało się zresetować urządzenia, albo graphicsDevice == null */
                    //{
                    //    this.ResetGraphics();
                    //}

                    //this.inDeviceTransition = false;
                }
            }
        }

        #endregion

        #region Internals

        private void OnRendering(object sender, EventArgs e)
        {
            if (!inDeviceTransition)
            {
                this.d3dimage.Lock();

                RenderRoutedEventArgs eventArgs = new RenderRoutedEventArgs(XnaControl.RenderEvent, this.graphicsDevice);
                this.RaiseEvent(eventArgs);

                this.d3dimage.AddDirtyRect(new Int32Rect(0, 0, d3dimage.PixelWidth, d3dimage.PixelHeight));
                this.d3dimage.Unlock();
            }
        }

        private void InitializeGraphics()
        {
            this.InitializeGraphicsDevice();
            this.InitializeRenderTarget();

            this.d3dimage.Lock();
            this.d3dimage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, XnaControl.GetRenderTargetSurfacePointer(this.renderTarget));
            this.d3dimage.Unlock();
        }

        private void ResetGraphics()
        {
            this.ResetGraphicsDevice();
            this.ResetRenderTarget();

            this.d3dimage.Lock();
            this.d3dimage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, XnaControl.GetRenderTargetSurfacePointer(this.renderTarget));
            this.d3dimage.Unlock();
        }

        private void ResetGraphicsDevice()
        {
            if (this.graphicsDevice != null)
            {
                this.graphicsDevice.Dispose();
                this.graphicsDevice = null;
            }

            this.graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.HiDef, presentationParameters);
        }

        private void ResetRenderTarget()
        {
            if (this.renderTarget != null)
            {
                this.renderTarget.Dispose();
                this.renderTarget = null;
            }

            this.InitializeRenderTarget();
        }

        private void InitializeGraphicsDevice()
        {
            try
            {
                this.deviceWindow = new HwndSource(0, 0, 0, 0, 0, "dummyDeviceWindow", IntPtr.Zero);

                presentationParameters = new PresentationParameters();
                presentationParameters.DepthStencilFormat = DepthFormat.Depth24Stencil8;
                presentationParameters.RenderTargetUsage = RenderTargetUsage.DiscardContents;
                presentationParameters.BackBufferHeight = (int)this.ActualHeight;
                presentationParameters.BackBufferWidth = (int)this.ActualWidth;
                presentationParameters.IsFullScreen = false;
                presentationParameters.BackBufferFormat = SurfaceFormat.Color;
                presentationParameters.DeviceWindowHandle = this.deviceWindow.Handle;

                this.graphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.HiDef, presentationParameters);

                //czy te eventy beda potrzebne? - my nie bedziemy zajmowac sie ladowaniem/zwalnianiem resource'ów
                //this.device.DeviceResetting += new EventHandler<EventArgs>(this.HandleDeviceResetting);
                //this.device.DeviceReset += new EventHandler<EventArgs>(this.HandleDeviceReset);
                //this.device.DeviceLost += new EventHandler<EventArgs>(this.HandleDeviceLost);
                //this.device.Disposing += new EventHandler<EventArgs>(this.HandleDisposing);
            }
            catch (NoSuitableGraphicsDeviceException)
            {
                throw;
            }
            catch (ArgumentException e)
            {
                throw new NoSuitableGraphicsDeviceException("The device creation parameters contain invalid configuration options.", e);
            }
            catch (Exception e)
            {
                throw new NoSuitableGraphicsDeviceException("Unable to create the graphics device.", e);
            }
        }

        private void InitializeRenderTarget()
        {
            if (this.renderTarget == null)
            {
                int width = (int)this.ActualWidth;
                int height = (int)this.ActualHeight;

                this.renderTarget = new RenderTarget2D(this.graphicsDevice, (int)this.ActualWidth, (int)this.ActualHeight);
                this.graphicsDevice.SetRenderTarget(this.renderTarget);
            }
            else
            {
                throw new Exception("Render target is already initialized.");
            }
        }

        private unsafe static IntPtr GetRenderTargetSurfacePointer(RenderTarget2D renderTarget)
        {
            //internal RenderTargetHelper helper;
            FieldInfo renderTargetHelper = typeof(RenderTarget2D).GetField("helper", BindingFlags.Instance | BindingFlags.NonPublic);

            //internal unsafe IDirect3DSurface9* GetRenderTargetSurface(CubeMapFace faceType)
            MethodInfo getRenderTargetSurface = renderTargetHelper.FieldType.GetMethod("GetRenderTargetSurface", BindingFlags.Instance | BindingFlags.NonPublic);

            //IDirect3DSurface9* surface
            object renderTargetSurface = getRenderTargetSurface.Invoke(renderTargetHelper.GetValue(renderTarget), new object[] { CubeMapFace.PositiveX });

            IntPtr renderTargetSurfacePointer = new IntPtr(Pointer.Unbox(renderTargetSurface));

            return renderTargetSurfacePointer;
        }

        #endregion
    }
}
